﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Input;
using RSSFeedReader.Commands;
using RSSFeedReader.Controls;
using RSSFeedReader.Data.Models;
using RSSFeedReader.Resources;
using Forms = System.Windows.Forms;

namespace RSSFeedReader.ViewModels
{
    /// <summary>
    /// Base class for ChannelViewModel functionalities.
    /// </summary>
    public abstract class ManageChannelBaseViewModel : ViewModelBase
    {
        #region Fields
        ObservableCollection<ChannelViewModel> _channels;

        ChannelViewModel _rootChannelViewModel;
        ChannelViewModel _currentChannel;

        DelegateCommand _newCommand;
        DelegateCommand _newFolderCommand;
        DelegateCommand _deleteCommand;
        DelegateCommand _okCommand;
        DelegateCommand _MoveToFolderCommand;
        #endregion

        #region Constructor
        /// <summary>
        /// Initialize a new instance of the ManageChannelBaseViewModel class.
        /// </summary>
        public ManageChannelBaseViewModel()
        {
            Initialize();
        }

        /// <summary>
        /// Initialize data.
        /// </summary>
        void Initialize()
        {
            _channels = new ObservableCollection<ChannelViewModel>();
            ChannelDataSource.Instance.ChannelFolderAdded += OnChannelFolderAdded;
            ChannelDataSource.Instance.ChannelAdded += OnChannelAdded;
            ChannelDataSource.Instance.ChannelMovedToFolder += OnMoveToFolder;
            ((RSSFeedReaderApplication)System.Windows.Application.Current).DragDropCompleted +=
                new EventHandler<RSSFeedReader.Controls.MyTreeViewDragEventArgs>(OnChannelDragDropCompleted);
            ChannelDataSource.Instance.ChannelMoved += new EventHandler<ChannelMovedEventArgs>(OnChannelMoved);

            _rootChannelViewModel = ChannelViewModel.CreateNew(
                new Channel
                {
                    Title = "Feeds",
                    ChannelType = ChannelType.Folder
                });
            _rootChannelViewModel.PropertyChanged += OnChannelViewModelPropertyChanged;
            _channels.Add(_rootChannelViewModel);

            foreach (Channel channel in ChannelDataSource.Instance.Channels)
            {
                var channelViewModel = ChannelViewModel.CreateNew(channel);
                InitializeChannelViewModel(channelViewModel);
                _rootChannelViewModel.Children.Add(channelViewModel);
                channelViewModel.Parent = _rootChannelViewModel;
            }

            _rootChannelViewModel.IsExpanded = true;
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the collection of type <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </summary>
        public ObservableCollection<ChannelViewModel> Channels
        {
            get { return _channels; }
        }

        /// <summary>
        /// Gets the currently selected <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> instance.
        /// </summary>
        public ChannelViewModel CurrentChannel
        {
            get { return _currentChannel; }
            set
            {
                if (_currentChannel == value)
                    return;

                _currentChannel = value;
                OnPropertyChanged("CurrentChannel");
            }
        }
        #endregion

        #region Commands

        #region AddNewFeedCommand
        /// <summary>
        /// AddNewFeed command to add a new RSS feed.
        /// </summary>
        public ICommand AddNewFeedCommand
        {
            get
            {
                if (_newCommand == null)
                {
                    _newCommand = new DelegateCommand(DoAddNewFeedCommand, CanDoAddNewFeedCommand);
                }
                return _newCommand;
            }
        }

        /// <summary>
        /// Determines of the AddNewFeedCommand can be executed.
        /// </summary>
        /// <returns>True if the AddNewFeedCommand can be executed.</returns>
        bool CanDoAddNewFeedCommand()
        {
            return ChannelDataSource.Instance.IsNetworkAvailable;
        }

        /// <summary>
        /// Executes the AddNewFeedCommand.
        /// </summary>
        void DoAddNewFeedCommand()
        {
            DisplayWindow.ShowAddNewFeedView();
        }
        #endregion

        #region NewFolderCommand
        /// <summary>
        /// Adds a new <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> of type
        /// <see cref="RSSFeedReader.Data.Models.ChannelType.Folder"/>.
        /// </summary>
        public ICommand NewFolderCommand
        {
            get
            {
                if (_newFolderCommand == null)
                    _newFolderCommand = new DelegateCommand(DoNewFolderCommand);

                return _newFolderCommand;
            }
        }

        /// <summary>
        /// Executes the NewFolderCommand.
        /// </summary>
        void DoNewFolderCommand()
        {
            if (_currentChannel == null || _currentChannel == _rootChannelViewModel)
            {
                DisplayWindow.ShowAddNewFolderView();
            }
            else
            {
                DisplayWindow.ShowAddNewFolderView(
                    ChannelDataSource.Instance.FindChannelById(_currentChannel.ChannelID));
            }
        }
        #endregion

        #region DeleteChannelCommand
        /// <summary>
        /// Deletes a Channel.
        /// </summary>
        public ICommand DeleteChannelCommand
        {
            get
            {
                if (_deleteCommand == null)
                    _deleteCommand = new DelegateCommand(DoDeleteChannelCommand, CanDoDeleteChannelCommand);

                return _deleteCommand;
            }
        }

        /// <summary>
        /// Determines if the DeleteChannelCommand can be executed.
        /// </summary>
        /// <returns>True if the DeleteChannelCommand can be executed, false otherwise.</returns>
        bool CanDoDeleteChannelCommand()
        {
            return _currentChannel != _rootChannelViewModel &&
                _currentChannel != null;
        }

        /// <summary>
        /// Executes the DeleteChannelCommand.
        /// </summary>
        void DoDeleteChannelCommand()
        {
            string message;

            if (_currentChannel.ChannelType == ChannelType.Feed)
            {
                message = Strings.DeleteFeedConfirmation;
            }
            else
            {
                message = Strings.DeleteFolderConfirmation;
            }

            Forms.DialogResult dialogResult = DisplayMessage.ShowMessage(
                message,
                Strings.ApplicationName,
                Forms.MessageBoxButtons.YesNo,
                Forms.MessageBoxIcon.Warning);

            if (dialogResult == Forms.DialogResult.Yes)
            {
                ChannelDataSource.Instance.RemoveChannel(_currentChannel.ChannelID);
                _currentChannel.Parent.Children.Remove(_currentChannel);
            }
        }
        #endregion

        #region MoveToFolderCommand
        /// <summary>
        /// Moves a Channel to a specified folder.
        /// </summary>
        public ICommand MoveToFolderCommand
        {
            get
            {
                if (_MoveToFolderCommand == null)
                    _MoveToFolderCommand = new DelegateCommand(DoMoveToFolderCommand, CanDoMoveToFolderCommand);

                return _MoveToFolderCommand;
            }
        }

        /// <summary>
        /// Executes the MoveToFolderCommand.
        /// </summary>
        void DoMoveToFolderCommand()
        {
            DisplayWindow.ShowMoveToFolderView(_currentChannel);
        }

        /// <summary>
        /// Determines if the MoveToFolderCommand can be executed.
        /// </summary>
        /// <returns>True if the MoveToFolderCommand can be executed, false otherwise.</returns>
        bool CanDoMoveToFolderCommand()
        {
            return _currentChannel != _rootChannelViewModel &&
               _currentChannel != null;
        }
        #endregion

        #region OKCommand
        /// <summary>
        /// Saves changes made.
        /// </summary>
        public ICommand OKCommand
        {
            get
            {
                if (_okCommand == null)
                    _okCommand = new DelegateCommand(DoOKCommand);

                return _okCommand;
            }
        }

        /// <summary>
        /// Executes the SaveCommand.
        /// </summary>
        public virtual void DoOKCommand()
        {
            OnRequestClose();
        }
        #endregion

        #endregion

        #region Protected Methods
        /// <summary>
        /// Returns an ObservableCollection of type ChannelViewModel that are ChannelType.Folder only.
        /// </summary>
        /// <returns>Returns an ObservableCollection of type ChannelViewModel that are ChannelType.Folder only.</returns>
        protected ObservableCollection<ChannelViewModel> GetChannelFolders()
        {
            foreach (Channel channel in ChannelDataSource.Instance.Channels)
            {
                if (channel.ChannelType == ChannelType.Feed)
                {
                    _rootChannelViewModel.Children.Remove(
                        Common.FindChannelViewModelById(channel.ChannelID, Channels.ToList()));
                }
                else
                {
                    GetChannelFoldersChildren(
                        Common.FindChannelViewModelById(channel.ChannelID, Channels.ToList()));
                }
            }

            return _channels;
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// Initialize the specified <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>.
        /// </summary>
        /// <param name="channelViewModel">The <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/> to initialize.</param>
        void InitializeChannelViewModel(ChannelViewModel channelViewModel)
        {
            channelViewModel.PropertyChanged += OnChannelViewModelPropertyChanged;
            foreach (ChannelViewModel channelVM in channelViewModel.Children)
            {
                channelVM.PropertyChanged += OnChannelViewModelPropertyChanged;
                InitializeChannelViewModel(channelVM);
            }
        }

        /// <summary>
        /// Filters for folders in the children of the ChannelViewModel specified.
        /// </summary>
        /// <param name="channelViewModel">The ChannelViewModel to search the children of.</param>
        private void GetChannelFoldersChildren(ChannelViewModel channelViewModel)
        {
            List<ChannelViewModel> channelsToRemove = new List<ChannelViewModel>();

            foreach (ChannelViewModel child in channelViewModel.Children)
            {
                if (child.ChannelType == ChannelType.Feed)
                {
                    // Store ChannelViewModels to remove in a temp List as we can't
                    // modify collection whilst iterating through it.
                    channelsToRemove.Add(child);
                }
                else
                {
                    GetChannelFoldersChildren(child);
                }
            }

            // Remove ChannelViewModels that were identified.
            foreach (ChannelViewModel channelVMToRemove in channelsToRemove)
            {
                channelViewModel.Children.Remove(channelVMToRemove);
            }
        }
        #endregion

        #region Event Handlers
        /// <summary>
        /// Event handler for when a channel folder is added.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelFolderAdded(object sender, ChannelFolderAddedEventArgs e)
        {
            if (e.Parent == null)
            {
                var newChannelViewModel = ChannelViewModel.CreateNew(e.NewFolder);
                InitializeChannelViewModel(newChannelViewModel);
                _rootChannelViewModel.Children.Add(newChannelViewModel);
            }
            else
            {
                var newChannelViewModelParent = Common.FindChannelViewModelById(e.Parent.ChannelID, Channels.ToList());
                var newChannelViewModel = ChannelViewModel.CreateNew(e.NewFolder);
                newChannelViewModel.Parent = newChannelViewModelParent;
                InitializeChannelViewModel(newChannelViewModel);
                newChannelViewModelParent.Children.Add(newChannelViewModel);
            }
        }

        /// <summary>
        /// Event handler for when a property of the <see cref="RSSFeedReader.ViewModels.ChannelViewModel"/>
        /// instance changes.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            string IsSelected = "IsSelected";
            ChannelViewModel channelVM = sender as ChannelViewModel;

            if (e.PropertyName == IsSelected)
            {
                _currentChannel = channelVM;
            }
        }

        /// <summary>
        /// Event handler for when a channel feed is added.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnChannelAdded(object sender, ChannelAddedEventArgs e)
        {
            if (e.Error != null)
                return;

            // Create the new ChannelViewModel and add to the _channelFolders collection.
            var channelViewModel = ChannelViewModel.CreateNew(e.NewChannel);
            InitializeChannelViewModel(channelViewModel);

            // Add to the _channelFolders collection.
            _rootChannelViewModel.Children.Add(channelViewModel);
        }

        /// <summary>
        /// Event handler for when a channel or folder is
        /// moved to another folder.
        /// </summary>
        /// <param name="sender">
        /// The source of the event.
        /// </param>
        /// <param name="e">
        /// Event arguments describing the event.
        /// </param>
        void OnMoveToFolder(object sender, ChannelMovedToFolderEventArgs e)
        {
            ChannelViewModel folderViewModel = null;
            ChannelViewModel channelViewModelToMove = null;

            channelViewModelToMove = Common.FindChannelViewModelById(e.ChannelToMove.ChannelID, _channels.ToList());

            if (channelViewModelToMove == null)
                return;

            if (e.DestinationFolder != null) // Could be null if moving to root
                folderViewModel = Common.FindChannelViewModelById(e.DestinationFolder.ChannelID, _channels.ToList());

            if (channelViewModelToMove.Parent != null && folderViewModel != null)
            {
                // Moving from folder to folder
                ChannelViewModel parent = Common.FindChannelViewModelById(channelViewModelToMove.Parent.ChannelID, _channels.ToList());
                parent.Children.Remove(channelViewModelToMove);
                folderViewModel.Children.Add(channelViewModelToMove);
                channelViewModelToMove.Parent = folderViewModel;
                folderViewModel.IsExpanded = true;
            }
            else if (folderViewModel != null)
            {
                // Coming from root to a folder
                Channels.Remove(channelViewModelToMove);
                folderViewModel.Children.Add(channelViewModelToMove);
                channelViewModelToMove.Parent = folderViewModel;
                folderViewModel.IsExpanded = true;
            }
            else
            {
                // Must be moving back to the root
                ChannelViewModel parent = Common.FindChannelViewModelById(channelViewModelToMove.Parent.ChannelID, _channels.ToList());
                parent.Children.Remove(channelViewModelToMove);
                channelViewModelToMove.Parent = null;
                Channels.Add(channelViewModelToMove);
            }
        }

        /// <summary>
        /// Event handler for when a channel is moved.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">Event arguments describing the event.</param>
        void OnChannelDragDropCompleted(object sender, MyTreeViewDragEventArgs e)
        {
            if (e.Destination is ChannelViewModel)
            {
                ChannelDataSource.Instance.MoveChannel(e.Source.ChannelID, ((ChannelViewModel)e.Destination).ChannelID);
            }
            else if (e.Destination is MainViewModel || e.Destination is ManageFeedsViewModel)
            {
                ChannelDataSource.Instance.MoveChannel(e.Source.ChannelID, Guid.Empty);
            }
            else
            {
                Debug.Fail("Object destination not expected type " + e.Destination.GetType().ToString());
            }
        }

        /// <summary>
        /// Event handler for when a channel is moved.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">Event arguments describing the event.</param>
        void OnChannelMoved(object sender, ChannelMovedEventArgs e)
        {
            ChannelViewModel channelToMove;
            ChannelViewModel channelToMoveParent;
            ChannelViewModel channelToMoveTo;
            ChannelViewModel channelToMoveToParent;
            int channelToMoveToIndex = -1;

            channelToMove = Common.FindChannelViewModelById(e.ChannelToMove.ChannelID, _channels.ToList());
            Debug.Assert(channelToMove != null);
            channelToMoveParent =
                channelToMove.Parent == null ? null : Common.FindChannelViewModelById(channelToMove.Parent.ChannelID, _channels.ToList());

            if (e.ChannelToMoveTo == null)
            {
                // Must be moving back to the root
                channelToMoveParent.Children.Remove(channelToMove);
                channelToMove.Parent = null;
                _channels.Add(channelToMove);
                return;
            }

            channelToMoveTo = Common.FindChannelViewModelById(e.ChannelToMoveTo.ChannelID, _channels.ToList());
            Debug.Assert(channelToMoveTo != null);
            channelToMoveToParent =
                channelToMoveTo.Parent == null ? null : Common.FindChannelViewModelById(channelToMoveTo.Parent.ChannelID, _channels.ToList());

            if (channelToMoveParent != null && channelToMoveToParent != null)
            {
                channelToMoveToIndex = channelToMoveToParent.Children.IndexOf(channelToMoveTo);
                if (channelToMoveParent == channelToMoveToParent)
                {
                    // Moving from within the same folder
                    if (channelToMoveTo == channelToMoveToParent.Children.Last())
                    {
                        channelToMoveParent.Children.Remove(channelToMove);
                        channelToMoveToParent.Children.Add(channelToMove);
                    }
                    else if (channelToMoveTo == channelToMoveToParent.Children.First())
                    {
                        channelToMoveParent.Children.Remove(channelToMove);
                        channelToMoveToParent.Children.Insert(0, channelToMove);
                    }
                    else
                    {
                        channelToMoveParent.Children.Remove(channelToMove);
                        channelToMoveToParent.Children.Insert(channelToMoveToIndex, channelToMove);
                    }
                    channelToMove.Parent = channelToMoveToParent;
                }
                else
                {
                    // Moving from another folder
                    channelToMoveParent.Children.Remove(channelToMove);

                    if (channelToMoveTo == channelToMoveToParent.Children.Last())
                    {
                        channelToMoveToParent.Children.Add(channelToMove);
                    }
                    else if (channelToMoveTo == channelToMoveToParent.Children.First())
                    {
                        channelToMoveToParent.Children.Insert(0, channelToMove);
                    }
                    else
                    {
                        channelToMoveToParent.Children.Insert(channelToMoveToIndex, channelToMove);
                    }
                    channelToMove.Parent = channelToMoveToParent;
                }
            }
            else if (channelToMoveParent == null && channelToMoveToParent != null)
            {
                // Coming from root to a folder
                channelToMoveToIndex = channelToMoveToParent.Children.IndexOf(channelToMoveTo);
                _channels.Remove(channelToMove);

                if (channelToMoveTo == channelToMoveToParent.Children.Last())
                {
                    channelToMoveToParent.Children.Add(channelToMove);
                }
                else if (channelToMoveTo == channelToMoveToParent.Children.First())
                {
                    channelToMoveToParent.Children.Insert(0, channelToMove);
                }
                else
                {
                    channelToMoveToParent.Children.Insert(channelToMoveToIndex + 1, channelToMove);
                }

                channelToMove.Parent = channelToMoveToParent;
            }
            else if (channelToMoveParent != null && channelToMoveToParent == null)
            {
                // Coming from folder to root at a specified feed
                channelToMoveToIndex = _channels.IndexOf(channelToMoveTo);
                channelToMoveParent.Children.Remove(channelToMove);

                if (channelToMoveTo == _channels.Last())
                {
                    _channels.Add(channelToMove);
                }
                else if (channelToMoveTo == _channels.First())
                {
                    _channels.Insert(0, channelToMove);
                }
                else
                {
                    _channels.Insert(channelToMoveToIndex + 1, channelToMove);
                }

                channelToMove.Parent = null;
            }
            else if (channelToMoveParent == null && channelToMoveToParent == null)
            {
                channelToMoveToIndex = _channels.IndexOf(channelToMoveTo);

                // Moving from within the same folder
                if (channelToMoveTo == _channels.Last())
                {
                    _channels.Remove(channelToMove);
                    _channels.Add(channelToMove);
                }
                else if (channelToMoveTo == _channels.First())
                {
                    _channels.Remove(channelToMove);
                    _channels.Insert(0, channelToMove);
                }
                else
                {
                    _channels.Remove(channelToMove);
                    _channels.Insert(channelToMoveToIndex, channelToMove);
                }
                channelToMove.Parent = channelToMoveToParent;
            }
            else
            {
                Debug.WriteLine("Unable to move channel");
                Logger.LogMessage(Strings.LogTypeFatalError, "Unable to move channel");
            }
        }
        #endregion
    }
}
